#include "libcomm.h"
static char _LOG_H [] = "EWNID";

////////////////////////////////////////////////////////////////////////// SilenceLogger

bool Log::SilenceLogger::Open(){ return true;}
void Log::SilenceLogger::Close(){}
bool Log::SilenceLogger::Write(int level, const char* content, int size){ return true;}

////////////////////////////////////////////////////////////////////////// DefaultLogger

Log::DefaultLogger::DefaultLogger(const char* path, bool daily)
	:m_path(path),m_daily(daily),m_stream(NULL){}

Log::DefaultLogger::~DefaultLogger(){ /*Close();*/ }

bool Log::DefaultLogger::Open()
{
	this->Close();

	String filePath;
	if(m_path.empty())
		m_path = Application::GetApplicationFilePath();
	m_filetime = Timer::Now();
	if(m_daily)
	{
		filePath = String::format("%s_%04d%02d%02d.log",
			(char*)m_path,m_filetime.year,m_filetime.month,m_filetime.day);
	}
	else
	{
		filePath = m_path;
	}
	
	m_stream = fopen((char*)filePath, "ab");
	if (NULL == m_stream)
	{
		printf("*** open log file \"%s\" failure: %s.\n",(char*)filePath,strerror(errno));
		return false;
	}
#ifdef __GNUC__
	fchmod(fileno(m_stream), 0666); // allow other user to write.
#endif

	return true;
}

void Log::DefaultLogger::Close()
{
	if(NULL != m_stream)
	{
		fclose(m_stream);
		m_stream = NULL;
	}
}

bool Log::DefaultLogger::Write(int level, const char* content, int size)
{
	if(level < MYLIB_LOG_ERR || level > MYLIB_LOG_DEBUG || NULL == content)
		return false;
	String sLog = WrapContent(level,content,size);
	return WriteContent(sLog,sLog.length());
}

bool Log::DefaultLogger::WriteContent(const char* content, int size)
{
	printf("%s",content);
	CommTime now = Timer::Now();
	if(m_daily)
	{
		if (now.day != m_filetime.day || now.month != m_filetime.month || now.year != m_filetime.year)
		{
			if(!this->Open())
				return false;
		}
	}
	if (NULL != m_stream && 1 == fwrite(content, size, 1, m_stream))
	{
		fflush(m_stream);
		return true;
	}
	return false;
}

String Log::DefaultLogger::WrapContent(int level, const char* content, int size)
{
	String sHeader;
	{
		CommTime now = Timer::Now();
		int tid = Thread::Id();
		String sDate = String::Empty;
		if(!m_daily)
			sDate = String::format("%04d%02d%02d-",now.year,now.month,now.day);
#ifdef COMM_MSVC
		sHeader = String::format("[%s%02d%02d%02d.%03d:%c:%05d]: ",sDate.get(),now.hour,now.minute,now.second,now.microsecond/1000,
			_LOG_H[level - MYLIB_LOG_ERR],tid);
#else
		uid_t uid = geteuid();
		sHeader = String::format("[%s%02d%02d%02d.%06d:%c:%05d:%d]: ",sDate.get(),now.hour,now.minute,now.second,now.microsecond,
			_LOG_H[level - MYLIB_LOG_ERR],tid,uid);
#endif
	}

	int nHeaderLength = sHeader.length();
	int nContentLength = size;
	if(-1 == nContentLength)
		nContentLength = strlen(content);
	// do not limit content length.
	//nContentLength = MYLIB_MIN(nContentLength,COMM_STRING_DEFAULT_LENGTH);
	String sLog(nHeaderLength + nContentLength + 2);
	{
		strncpy(sLog,sHeader,nHeaderLength);
		strncpy((char*)sLog+nHeaderLength,content,nContentLength);
		nContentLength = sLog.length();
		char lastChar = sLog[nContentLength-1];
		if(lastChar == '\n')
		{
			if(sLog[nContentLength-2] == '\r')
			{
				sLog[nContentLength-2] = '\n';
				sLog[nContentLength-1] = 0;
			}
		}
		else
		{
			sLog[nContentLength] = '\n';
			sLog[nContentLength+1] = 0;
		}
	}

	return sLog;
}

////////////////////////////////////////////////////////////////////////// CachedLogger

Log::CachedLogger::CachedLogger(const char* path, bool daily, size_t maxCapacity, int intervalMs)
	:DefaultLogger(path,daily),m_queue(maxCapacity),m_intervalMs(intervalMs),m_continue(1),m_thread(NULL)
{
	m_thread = Thread::Run(PullProc,(void*)this,"log_writting_trd");
	MYLIB_ASSERT_SILENCE(NULL!=m_thread);
}

Log::CachedLogger::~CachedLogger()
{
	if(NULL != m_thread)
	{
		m_continue = 0;
		m_thread->Join();
		delete m_thread;
		m_thread = NULL;
	}
}

int Log::CachedLogger::PullProc(CommType& arg)
{
	Log::CachedLogger* logger = (Log::CachedLogger*)((void*)arg);
	while (logger->m_continue == 1)
	{
		ScopedPtr<Buffer> item(logger->m_queue.Pop(logger->m_intervalMs));
		if(item == NULL)
			continue;

		logger->WriteContent(item->get(),strlen(item->get()));
	}
	return 0;
}

bool Log::CachedLogger::Write(int level, const char* content, int size)
{
	if(level < MYLIB_LOG_ERR || level > MYLIB_LOG_DEBUG || NULL == content)
		return false;
	String sLog = WrapContent(level,content,size);
	return m_queue.Push(sLog,false,m_intervalMs);
}


////////////////////////////////////////////////////////////////////////// StdoutLogger

Log::StdoutLogger::StdoutLogger(bool shortDate):DefaultLogger(NULL,shortDate){}
bool Log::StdoutLogger::Open(){ return true;}
void Log::StdoutLogger::Close(){}
bool Log::StdoutLogger::WriteContent(const char* content, int size)
{
	(void*)&size;
	printf("%s",content);
	return true;
}


////////////////////////////////////////////////////////////////////////// Log


Log::Log(void)
	: m_level(MYLIB_LOG_DEBUG)
	, m_logger(NULL)
{
}

Log::~Log(void)
{
	SetLogger(NULL);
}

void Log::SetLogger(Log::Logger* logger)
{
	if(NULL == logger)
	{
		m_level = 0; // do not write log any more.
	}
	{
		ThreadLockGuard guard(&m_lock);
		if (NULL != m_logger)
		{
			m_logger->Close();
			m_logger->Release();
			m_logger = NULL;
		}
		if(NULL != logger && logger->Open())
			m_logger = logger;
	}
	
}

Log::Logger* Log::GetLogger()
{
	ThreadLockGuard guard(&m_lock);
	return m_logger;
}

void Log::SetLevel(int level)
{
	m_level = level;
}


bool Log::Write(int level, const char* format, ...)
{
	bool ret;
	va_list args;
	va_start(args, format);
	ret = VWrite(level, format, args);
	va_end(args);
	return ret;
}


bool Log::VWrite(int level, const char* format, va_list args)
{
	if (m_level >= level)
	{
		ThreadLockGuard guard(&m_lock);
		String content(COMM_STRING_DEFAULT_LENGTH);
		content.vsprintf(format,args);
		if (NULL == m_logger)
			SetLogger(new Log::DefaultLogger());
		MYLIB_ASSERT_SILENCE(NULL != m_logger);
		return m_logger->Write(level, content, content.length());
	}
	return false;
}